package com.koron.plan.web.patrolplan.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import com.koron.bean.base.Response;
import com.koron.bean.plan.patrol.dto.PatrolDto;
import com.koron.bean.plan.patrol.po.PatrolEquipmentItemPo;
import com.koron.bean.plan.patrol.po.PatrolEquipmentParamsPo;
import com.koron.bean.plan.patrol.po.PatrolUserPo;
import com.koron.bean.plan.patrol.vo.PatrolEnvCheckVo;
import com.koron.bean.plan.patrol.vo.PatrolEquipmentVo;
import com.koron.bean.plan.patrol.vo.PatrolPointVo;
import com.koron.bean.system.staff.vo.EamUser;
import com.koron.common.core.config.ThreadLocalContext;
import com.koron.common.core.util.CodeTools;
import com.koron.common.core.util.OrgCacheUtils;
import com.koron.common.core.util.StringUtils;
import com.koron.common.core.util.message.MessageUtil;
import com.koron.common.task.web.entity.CronTask;
import com.koron.common.task.web.entity.XxlJobInfo;
import com.koron.common.task.web.service.XxlJobService;
import com.koron.plan.config.Constants;
import com.koron.plan.web.common.CommonUtils;
import com.koron.plan.web.feign.EquipmentFeign;
import com.koron.plan.web.feign.OrderFeign;
import com.koron.plan.web.feign.ShiftFeign;
import com.koron.plan.web.feign.dto.EquipmentLocation;
import com.koron.plan.web.patrolplan.bean.convertor.PatrolPlanConvertor;
import com.koron.plan.web.patrolplan.bean.dto.ChangeShiftDTO;
import com.koron.plan.web.patrolplan.bean.dto.ClassesShiftDTO;
import com.koron.plan.web.patrolplan.bean.dto.ClassesStaff;
import com.koron.plan.web.patrolplan.bean.entity.PatrolPlanBean;
import com.koron.plan.web.patrolplan.bean.entity.PatrolPlanWorkerBean;
import com.koron.plan.web.patrolplan.bean.query.PatrolPlanQuery;
import com.koron.plan.web.patrolplan.bean.query.ShiftRecordQuery;
import com.koron.plan.web.patrolplan.bean.vo.PatrolPlanDetailVO;
import com.koron.plan.web.patrolplan.bean.vo.PatrolPlanPointDetailVO;
import com.koron.plan.web.patrolplan.bean.vo.PatrolPlanPointRelVO;
import com.koron.plan.web.patrolplan.bean.vo.PatrolPlanVO;
import com.koron.plan.web.patrolplan.mapper.PatrolPlanMapper;
import com.koron.plan.web.patrolplan.mapper.PatrolPlanPointRelMapper;
import com.koron.plan.web.patrolplan.service.PatrolPlanPointService;
import com.koron.plan.web.patrolplan.service.PatrolPlanService;
import com.koron.plan.web.patrolplan.service.PatrolPlanWorkerService;
import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.koron.ebs.mybatis.SessionFactory;
import org.koron.ebs.mybatis.TaskAnnotation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author twcao
 * @title: PatrolPlanServiceImpl
 * @projectName GDH_EAM_V2
 * @description: 巡视计划service
 * @date 2021/10/279:29
 */
@Slf4j
@Service
public class PatrolPlanServiceImpl implements PatrolPlanService {

    @Autowired
    private PatrolPlanWorkerService workerService;

    @Autowired
    private OrderFeign orderFeign;

    @Autowired
    private OrgCacheUtils orgUtils;

    @Autowired
    private XxlJobService xxlJobService;

    @Autowired
    private PatrolPlanPointService pointService;

    @Autowired
    private PatrolPlanConvertor convertor;

    @Autowired
    private MessageUtil messageUtil;

    @Autowired
    private ShiftFeign shiftFeign;

    @Autowired
    private EquipmentFeign equipmentFeign;

    /**
     * 通过ID查询单条数据
     *
     * @param factory 会话
     * @param pointId 巡视点id
     * @return 实例对象
     */
    @Override
    @TaskAnnotation("queryById")
    public PatrolPlanDetailVO queryById(SessionFactory factory, String pointId) {
        PatrolPlanMapper mapper = factory.getMapper(PatrolPlanMapper.class);
        PatrolPlanDetailVO detail = mapper.queryById(pointId);
        detail.setCronTask(xxlJobService.getCronTaskById(detail.getId()));
        return detail;
    }

    /**
     * 查询多条数据
     *
     * @param factory 会话
     * @param query   查询条件
     * @return 对象列表
     */
    @Override
    @TaskAnnotation("queryList")
    public List<PatrolPlanVO> queryList(SessionFactory factory, PatrolPlanQuery query) {
        PatrolPlanMapper mapper = factory.getMapper(PatrolPlanMapper.class);
        List<PatrolPlanVO> plans = mapper.queryList(query);
        return plans;
    }

    /**
     * 下发巡视计划，生成工单
     *
     * @param factory 会话
     * @param id      巡视计划id
     * @return
     */
    @Override
    @TaskAnnotation("issue")
    public PatrolPlanDetailVO issue(SessionFactory factory, String id) {
        PatrolPlanDetailVO detailVO = queryById(factory, id);
        Assert.isTrue(Constants.PATROL_PLAN_STATE.USING.equals(detailVO.getState()), "计划处于草稿状态，无法下发");
        log.info("手动下发巡视计划，生成工单");
        // 如果是手动下发，则计划开始时间不将提前下发计算在内
        detailVO.setAdvanceHours(0.0);
        boolean flag = generatePatrolOrder(detailVO);
        Assert.isTrue(flag, "巡视工单生成失败");
        return detailVO;
    }

    /**
     * 启用或关闭巡视计划
     *
     * @param factory     会话
     * @param id          巡视计划id
     * @param startOrStop 启动或停止标志，startOrStop = true，启用巡视计划(已启用)。startOrStop = false，停用巡视计划(草稿)
     * @return 修改后的详情
     */
    @Override
    @TaskAnnotation("startOrStop")
    public PatrolPlanDetailVO startOrStop(SessionFactory factory, String id, Boolean startOrStop) {
        PatrolPlanMapper mapper = factory.getMapper(PatrolPlanMapper.class);
        PatrolPlanDetailVO detailVO = queryById(factory, id);
        if (Constants.YesOrNo.YES.equals(detailVO.getIsTimer())) {
            if (startOrStop) {
                // 已启用，且定时下达
                if (!CommonUtils.isPositive(detailVO.getAdvanceHours())) {
                    // 1、不提前 ---> 启动定时任务
                    xxlJobService.start(id);
                } else {
                    xxlJobService.start(id);
                    // 2、提前 ---> 创建一个定点执行的任务（只执行一次的任务）
                    Assert.isTrue(Constants.PATROL_PLAN_STATE.DRAFT.equals(detailVO.getState()), "任务已处于启用状态");
                    Date firstTaskTime = getFirstExecuteTimes(detailVO.getCronTask().getCron(), detailVO.getAdvanceHours());
                    String executeTCron = CommonUtils.getCron(firstTaskTime);
                    CronTask cronTask = xxlJobService.getCronTaskById(id);
                    XxlJobInfo jobInfo = xxlJobService.getXxlJobInfoById(cronTask.getXxlJobId());
                    xxlJobService.addSimpleJob(String.valueOf(cronTask.getXxlJobId()), executeTCron, cronTask.getMark(), "jobId=" + cronTask.getXxlJobId() + "的临时一次性任务", detailVO.getUpdateBy(), jobInfo.getExecutorHandler(), 30);
                    xxlJobService.start(String.valueOf(cronTask.getXxlJobId()));
                }
                detailVO.setState(Constants.PATROL_PLAN_STATE.USING);
            } else {
                Assert.isTrue(Constants.PATROL_PLAN_STATE.USING.equals(detailVO.getState()), "任务已处于停用状态");
                xxlJobService.pause(id);
                if (CommonUtils.isPositive(detailVO.getAdvanceHours())) {
                    // 如果是临时任务，则移除临时任务
                    CronTask cronTask = xxlJobService.getCronTaskById(id);
                    if (cronTask != null && cronTask.getXxlJobId() != null) {
                        // 任务必须要移除成功，则否报出异常，防止脏数据
                        Assert.isTrue(xxlJobService.remove(String.valueOf(cronTask.getXxlJobId())), "提前任务移除失败");
                    }
                }
                detailVO.setState(Constants.PATROL_PLAN_STATE.DRAFT);
            }
        }
        detailVO.setState(startOrStop ? 1 : 0);
        mapper.saveOrUpdate(detailVO);
        return detailVO;
    }

    /**
     * 新增/修改数据
     *
     * @param factory
     * @param bean    实例对象
     * @return 实例对象
     */
    @Override
    public boolean saveOrUpdate(SessionFactory factory, PatrolPlanBean bean) {
        PatrolPlanMapper mapper = factory.getMapper(PatrolPlanMapper.class);
        EamUser user = ThreadLocalContext.get();
        bean.setDeptId(StringUtils.isBlank(bean.getDeptId()) ? user.getOrgId() : bean.getDeptId());
        CommonUtils.update(bean, user.getId(), user.getName());
        CommonUtils.update(bean);
        bean.setId(StringUtils.isBlank(bean.getId()) ? CodeTools.getCode32() : bean.getId());
        bean.setCode(StringUtils.isBlank(bean.getCode()) ? CommonUtils.getCodeNo(Constants.PATROL_PREFIX, 3) : bean.getCode());
        bean.setDeptName(orgUtils.getOrgNameById(bean.getDeptId()));
        bean.setPlanDeptName(orgUtils.getOrgNameById(bean.getPlanDeptId()));
        bean.setPatrolDeptName(orgUtils.getOrgNameById(bean.getPatrolDeptId()));
        return mapper.saveOrUpdate(bean) > 0;
    }

    /**
     * 新增/修改数据
     *
     * @param factory
     * @param detail  实例对象详情
     * @return 实例对象
     */
    @Override
    @TaskAnnotation("saveOrUpdate")
    public PatrolPlanDetailVO saveOrUpdate(SessionFactory factory, PatrolPlanDetailVO detail) {
        PatrolPlanPointRelMapper relMapper = factory.getMapper(PatrolPlanPointRelMapper.class);
        // 1、保存巡视计划主体
        saveOrUpdate(factory, (PatrolPlanBean) detail);
        // 设置cron表达式
        if (Constants.YesOrNo.YES.equals(detail.getIsTimer())) {
            detail.getCronTask().setCron(CommonUtils.parseStrToCron(detail.getCronTask()));
        }
        // 2、保存巡视计划和巡视点关系
        List<PatrolPlanPointRelVO> rels = detail.getRels();
        if (CollectionUtils.isNotEmpty(rels)) {
            relMapper.deleteByPlanId(detail.getId());
            rels.stream().forEach(rel -> {
                rel.setId(CodeTools.getCode32());
                rel.setPlanId(detail.getId());
            });
            CommonUtils.sorted(rels);
            CommonUtils.update(rels);
            relMapper.insertBatch(rels);
        }
        // 3、保存巡视人员列表
        List<PatrolPlanWorkerBean> workers = detail.getWorkers();
        workers.stream().forEach(worker -> worker.setPlanId(detail.getId()));
        workerService.deleteByPlanId(factory, detail.getId());
        workerService.saveOrUpdate(factory, workers);
        // 4、设置定时任务表达式(detail.getIsTimer() = 1时，cron不能为空)
        if (Constants.YesOrNo.YES.equals(detail.getIsTimer())) {
            // 定时下达 = true，新增/更新任务
            Assert.notNull(detail.getCronTask(), "定时任务不能为空");
            CronTask cronTask = xxlJobService.getCronTaskById(detail.getId());
            if (cronTask != null) {
                // 更新任务
                xxlJobService.update(detail.getId(), detail.getCronTask().getCron(), factory.getDbEnv(), null, detail.getUpdateBy(), null);
            } else {
                // 新增任务 创建人名字[时间]巡视计划
                String jobName = detail.getUpdateBy() + "的巡视计划[" + DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm") + "]";
                xxlJobService.addSimpleJob(detail.getId(), detail.getCronTask().getCron(), factory.getDbEnv(), jobName, detail.getUpdateBy(), "generatePatrolOrder", 30);
            }
        } else {
            // 如果不是定时任务，则删除任务（之前为定时任务时，需要删除）
            xxlJobService.remove(detail.getId());
        }
        return queryById(factory, detail.getId());
    }

    /**
     * 通过计划id删除数据
     *
     * @param planId 计划id
     * @return 是否成功
     */
    @Override
    @TaskAnnotation("deleteById")
    public boolean deleteById(SessionFactory factory, String planId) {
        return false;
    }

    /**
     * 通过计划id逻辑删除数据
     *
     * @param planId 计划id
     * @return 是否成功
     */
    @Override
    @TaskAnnotation("hiddenById")
    public boolean hiddenById(SessionFactory factory, String planId) {
        PatrolPlanMapper mapper = factory.getMapper(PatrolPlanMapper.class);
        PatrolPlanPointRelMapper relMapper = factory.getMapper(PatrolPlanPointRelMapper.class);
        mapper.hiddenById(planId);
        relMapper.hiddenByPlanId(planId);
        workerService.hiddenByPlanId(factory, planId);
        xxlJobService.remove(planId);
        return true;
    }

    public boolean createPatrolOrder(PatrolPlanDetailVO detail) {
        long now = System.currentTimeMillis();
        try (SessionFactory factory = new SessionFactory()) {
            factory.setDbEnv(ThreadLocalContext.get().getCurrDs());
            XxlJobHelper.log("当前数据源: {}", ThreadLocalContext.get().getCurrDs());
            PatrolDto patrolDto = convertor.planBasicToOrder(detail);
            List<PatrolPointVo> pointVos = new ArrayList<>();
            patrolDto.setPointVos(pointVos);
            // 默认待巡视状态
            patrolDto.setStatus(Constants.PATROL_ORDER_INIT_STATUS);
            // 1、计划主体
            // {巡视计划标题}_{YYYYMMDDHH}
            patrolDto.setTitle(detail.getName() + "_" + DateFormatUtils.format(new Date(), "yyyyMMddHH"));
            // 提前的小时数
            Double advanceHours = detail.getAdvanceHours();
            // 如果有提前时间，则往后推迟指定时间，计作计划开始时间
            Date startTime = new Date(now + (advanceHours == null ? 0 : new Double((advanceHours * 3600 * 1000)).longValue()));
            patrolDto.setPlanStartTime(startTime);
            patrolDto.setPlanEndTime(new Date(startTime.getTime() + new Double(detail.getPatrolDuration() * 3600 * 1000).longValue()));
            patrolDto.setCreateTime(new Date());
            patrolDto.setUpdateTime(new Date());
            // 2、巡视点
            List<PatrolPlanPointRelVO> rels = detail.getRels();
            // 所有巡视点id
            List<String> pointIds = rels.stream().map(PatrolPlanPointRelVO::getPointId).collect(Collectors.toList());
            // 批量查询所有的巡视点
            List<PatrolPlanPointDetailVO> pointDetailVo = pointService.queryBatchById(factory, pointIds);
            // 巡视点 id -> bean
            Map<String, PatrolPlanPointDetailVO> pointMap = pointDetailVo.stream().collect(Collectors.toMap(PatrolPlanPointDetailVO::getId, item -> item, (v1, v2) -> v2));
            // 所有巡视点的位置id
            List<String> locationIds = pointDetailVo.stream().map(PatrolPlanPointDetailVO::getLocationId).collect(Collectors.toList());
            // 批量查询所有巡视点的位置
            List<EquipmentLocation> locations = CommonUtils.ok(equipmentFeign.getByIdWithoutEquipment(locationIds));
            // 巡视点位置 id -> bean
            Map<String, EquipmentLocation> locationMap = locations.stream().collect(Collectors.toMap(EquipmentLocation::getId, item -> item, (v1, v2) -> v2));
            for (PatrolPlanPointRelVO rel : rels) {
                PatrolPlanPointDetailVO patrolPlanPointDetailVO = pointMap.get(rel.getPointId());
                PatrolPointVo patrolPointVo = convertor.pointDetailToOrderPoint(patrolPlanPointDetailVO);
                // 设置顺序
                patrolPointVo.setSort(rel.getSort());
                EquipmentLocation location = locationMap.get(patrolPlanPointDetailVO.getLocationId());
                XxlJobHelper.log("巡视计划-巡视设备位置: {}", JSON.toJSONString(location));
                if (!Objects.isNull(location) && StringUtils.isNotEmpty(location.getLatlon()) && StringUtils.split(location.getLatlon(), ",").length == 2) {
                    // 校验经纬度存在且可正确拿到，latlon格式：经度,纬度
                    patrolPointVo.setLongitude(StringUtils.split(location.getLatlon(), ",")[0]);
                    patrolPointVo.setLatitude(StringUtils.split(location.getLatlon(), ",")[1]);
                    patrolPointVo.setRange(location.getRange());
                }
                pointVos.add(patrolPointVo);
            }
            // 3、巡视-值班人员
            if (Constants.StaffingWay.map.containsKey(detail.getStaffingWay())) {
                // 没有指定人员
                List<String> staffTypes = new ArrayList<>();
                staffTypes.add(Constants.StaffingWay.map.get(detail.getStaffingWay()));
                ShiftRecordQuery shiftQuery = new ShiftRecordQuery();
                // 条件1：部门
                shiftQuery.setDeptId(detail.getPatrolDeptId());
                // 条件2：位置
                shiftQuery.setLocationId(detail.getLocationId());
                // 条件3：排班方式
                shiftQuery.setStaffTypes(staffTypes);
                ChangeShiftDTO shiftDTO = CommonUtils.ok(shiftFeign.info(shiftQuery));
                if (shiftDTO != null && shiftDTO.getThisClassesShift() != null) {
                    ClassesShiftDTO thisShift = shiftDTO.getThisClassesShift();
                    List<ClassesStaff> staffs = thisShift.getStaffs();
                    // 如果巡视工单的预计开始时间在本值结束时间之前，则将本值人员设置为巡视人员
                    // if(thisShift.getEndTime().compareTo(patrolDto.getPlanStartTime()) > 0 && !CollectionUtils.isEmpty(staffs)) {
                    if (!CollectionUtils.isEmpty(staffs)) {
                        List<PatrolUserPo> userPos = new ArrayList<>();
                        Stream.iterate(0, i -> i + 1).limit(staffs.size()).forEach(i -> {
                            ClassesStaff staff = staffs.get(i);
                            userPos.add(new PatrolUserPo(staff.getStaffBy(), staff.getStaffName(), i + 1));
                        });
                        patrolDto.setUserPos(userPos);
                    } else {
                        throw new IllegalArgumentException("未找到值班人员");
                    }
                } else {
                    throw new IllegalArgumentException("未找到值班人员");
                }
            } else if (Objects.equals(Constants.StaffingWay.SHIFT_FORM, detail.getStaffingWay())) {
                // 如果是根据排班表
                ChangeShiftDTO shiftDTO = CommonUtils.ok(shiftFeign.shiftInfo(detail.getPatrolDeptId(), detail.getLocationId(), DateUtil.today()));
                if (shiftDTO != null && shiftDTO.getThisClassesShift() != null) {
                    ClassesShiftDTO thisShift = shiftDTO.getThisClassesShift();
                    List<ClassesStaff> staffs = thisShift.getStaffs();
                    // 如果巡视工单的预计开始时间在本值结束时间之前，则将本值人员设置为巡视人员
                    // if(thisShift.getEndTime().compareTo(patrolDto.getPlanStartTime()) > 0 && !CollectionUtils.isEmpty(staffs)) {
                    if (!CollectionUtils.isEmpty(staffs)) {
                        List<PatrolUserPo> userPos = new ArrayList<>();
                        Stream.iterate(0, i -> i + 1).limit(staffs.size()).forEach(i -> {
                            ClassesStaff staff = staffs.get(i);
                            userPos.add(new PatrolUserPo(staff.getStaffBy(), staff.getStaffName(), i + 1));
                        });
                        patrolDto.setUserPos(userPos);
                    } else {
                        throw new IllegalArgumentException("未找到值班人员");
                    }
                } else {
                    throw new IllegalArgumentException("未找到值班人员");
                }
            } else if (!Constants.StaffingWay.map.containsKey(detail.getStaffingWay())) {
                // 指定巡视人员
                List<PatrolUserPo> userPos = new ArrayList<>();
                List<PatrolPlanWorkerBean> workers = detail.getWorkers();
                Stream.iterate(0, i -> i + 1).limit(workers.size()).forEach(i -> {
                    PatrolPlanWorkerBean worker = workers.get(i);
                    userPos.add(new PatrolUserPo(worker.getWorkerId(), worker.getWorkerName(), i + 1));
                });
                patrolDto.setUserPos(userPos);
            }
            //处理生成的工单数据
            dealPatrolDto(patrolDto);
            log.info("生成巡视工单， dto = {}", JSON.toJSONString(patrolDto));
            Response<String> response = orderFeign.insertOrUpdate(patrolDto);
            Object ok = CommonUtils.ok(response);
            log.info("生成巡视工单响应， response = {}", response.toJson());
            // 通知人员去巡视
            topicPatrol(BeanUtil.toBean(ok, PatrolDto.class));
            return true;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return false;
        }
    }


    /**
     * 解决定时任务生成经常失败问题
     * 采用先生成主表巡视单的内容，后续打开详情的时候才生成具体的子表内容
     *
     * @param detail
     * @return
     */
    public boolean createPatrolOrderOnly(PatrolPlanDetailVO detail) {
        long now = System.currentTimeMillis();
        try (SessionFactory factory = new SessionFactory()) {
            factory.setDbEnv(ThreadLocalContext.get().getCurrDs());
            XxlJobHelper.log("开始创建子表数据,当前数据源: {}", ThreadLocalContext.get().getCurrDs());
            PatrolDto patrolDto = convertor.planBasicToOrder(detail);
            // 默认待巡视状态
            patrolDto.setStatus(Constants.PATROL_ORDER_INIT_STATUS);
            // 1、计划主体
            // {巡视计划标题}_{YYYYMMDDHH}
            patrolDto.setTitle(detail.getName() + "_" + DateFormatUtils.format(new Date(), "yyyyMMddHH"));
            // 提前的小时数
            Double advanceHours = detail.getAdvanceHours();
            // 如果有提前时间，则往后推迟指定时间，计作计划开始时间
            Date startTime = new Date(now + (advanceHours == null ? 0 : new Double((advanceHours * 3600 * 1000)).longValue()));
            patrolDto.setPlanStartTime(startTime);
            patrolDto.setPlanEndTime(new Date(startTime.getTime() + new Double(detail.getPatrolDuration() * 3600 * 1000).longValue()));
            patrolDto.setCreateTime(new Date());
            patrolDto.setUpdateTime(new Date());

            patrolDto.setId(null);
            patrolDto.setPointNum(detail.getRels().size());
            patrolDto.setInsertFlag(1);


            // 3、巡视-值班人员
            if (Constants.StaffingWay.map.containsKey(detail.getStaffingWay())) {
                // 没有指定人员
                List<String> staffTypes = new ArrayList<>();
                staffTypes.add(Constants.StaffingWay.map.get(detail.getStaffingWay()));
                ShiftRecordQuery shiftQuery = new ShiftRecordQuery();
                // 条件1：部门
                shiftQuery.setDeptId(detail.getPatrolDeptId());
                // 条件2：位置
                shiftQuery.setLocationId(detail.getLocationId());
                // 条件3：排班方式
                shiftQuery.setStaffTypes(staffTypes);
                ChangeShiftDTO shiftDTO = CommonUtils.ok(shiftFeign.info(shiftQuery));
                if (shiftDTO != null && shiftDTO.getThisClassesShift() != null) {
                    ClassesShiftDTO thisShift = shiftDTO.getThisClassesShift();
                    List<ClassesStaff> staffs = thisShift.getStaffs();
                    // 如果巡视工单的预计开始时间在本值结束时间之前，则将本值人员设置为巡视人员
                    // if(thisShift.getEndTime().compareTo(patrolDto.getPlanStartTime()) > 0 && !CollectionUtils.isEmpty(staffs)) {
                    if (!CollectionUtils.isEmpty(staffs)) {
                        List<PatrolUserPo> userPos = new ArrayList<>();
                        Stream.iterate(0, i -> i + 1).limit(staffs.size()).forEach(i -> {
                            ClassesStaff staff = staffs.get(i);
                            userPos.add(new PatrolUserPo(staff.getStaffBy(), staff.getStaffName(), i + 1));
                        });
                        patrolDto.setUserPos(userPos);
                    } else {
                        XxlJobHelper.log("未找到值班人员：{}+{}", shiftQuery.getDeptName(), shiftQuery.getLocationName());
                    }
                } else {
                    XxlJobHelper.log("未找到值班人员：{}+{}", shiftQuery.getDeptName(), shiftQuery.getLocationName());
                }
            } else if (Objects.equals(Constants.StaffingWay.SHIFT_FORM, detail.getStaffingWay())) {
                // 如果是根据排班表
                ChangeShiftDTO shiftDTO = CommonUtils.ok(shiftFeign.shiftInfo(detail.getPatrolDeptId(), detail.getLocationId(), DateUtil.today()));
                if (shiftDTO != null && shiftDTO.getThisClassesShift() != null) {
                    ClassesShiftDTO thisShift = shiftDTO.getThisClassesShift();
                    List<ClassesStaff> staffs = thisShift.getStaffs();
                    // 如果巡视工单的预计开始时间在本值结束时间之前，则将本值人员设置为巡视人员
                    // if(thisShift.getEndTime().compareTo(patrolDto.getPlanStartTime()) > 0 && !CollectionUtils.isEmpty(staffs)) {
                    if (!CollectionUtils.isEmpty(staffs)) {
                        List<PatrolUserPo> userPos = new ArrayList<>();
                        Stream.iterate(0, i -> i + 1).limit(staffs.size()).forEach(i -> {
                            ClassesStaff staff = staffs.get(i);
                            userPos.add(new PatrolUserPo(staff.getStaffBy(), staff.getStaffName(), i + 1));
                        });
                        patrolDto.setUserPos(userPos);
                    } else {
                        throw new IllegalArgumentException("未找到值班人员");
                    }
                } else {
                    throw new IllegalArgumentException("未找到值班人员");
                }
            } else if (!Constants.StaffingWay.map.containsKey(detail.getStaffingWay())) {
                // 指定巡视人员
                List<PatrolUserPo> userPos = new ArrayList<>();
                List<PatrolPlanWorkerBean> workers = detail.getWorkers();
                Stream.iterate(0, i -> i + 1).limit(workers.size()).forEach(i -> {
                    PatrolPlanWorkerBean worker = workers.get(i);
                    userPos.add(new PatrolUserPo(worker.getWorkerId(), worker.getWorkerName(), i + 1));
                });
                patrolDto.setUserPos(userPos);
            }


            log.info("生成巡视工单主表， dto = {}", JSON.toJSONString(patrolDto));
            Response<Object> response = orderFeign.insertOrder(patrolDto);
            Object ok = CommonUtils.ok(response);
            log.info("生成巡视工单主表响应， response = {}", response.toJson());
            XxlJobHelper.log("主表创建成功");
            // 通知人员去巡视
            topicPatrol(BeanUtil.toBean(ok, PatrolDto.class));
            return true;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            XxlJobHelper.log("主表创建时报错,错误信息：", e.getStackTrace());
            return false;
        }
    }


    /**
     * 巡视计划下发巡视工单时进行通知
     */
    private void topicPatrol(PatrolDto patrolDto) {
        try {
            // 您好，有巡视工单任务下发给您，请及时巡视。 工单标题：%s。 工单编号：%s。
            if (CollectionUtils.isNotEmpty(patrolDto.getUserPos())) {
                String userStr = patrolDto.getUserPos().stream().map(PatrolUserPo::getWorker).collect(Collectors.joining(","));
                messageUtil.sendMessageWithTemplate("patrol", null, userStr, patrolDto.getTitle(), patrolDto.getCode());
            }
        } catch (Exception e) {
            log.error("巡视工单下发时通知失败");
        }
    }

    /*对部分字段信息进行抹除*/
    private void dealPatrolDto(PatrolDto patrolDto) {
        if (Objects.isNull(patrolDto) || CollectionUtils.isEmpty(patrolDto.getPointVos())) {
            return;
        }
        patrolDto.setId(null);
        List<PatrolPointVo> pointVos = patrolDto.getPointVos();

        pointVos.forEach(p -> {
            p.setId(null);
            List<PatrolEnvCheckVo> envCheckVos = p.getEnvCheckVos();
            if (CollectionUtils.isNotEmpty(envCheckVos)) {
                envCheckVos.forEach(t -> t.setId(null));
            }
            List<PatrolEquipmentVo> equipmentVos = p.getEquipmentVos();
            if (CollectionUtils.isNotEmpty(equipmentVos)) {
                equipmentVos.forEach(t -> {
                    t.setId(null);
                    List<PatrolEquipmentItemPo> equipmentItemPos = t.getEquipmentItemPos();
                    if (CollectionUtils.isNotEmpty(equipmentItemPos)) {
                        equipmentItemPos.forEach(itemPo -> itemPo.setId(null));
                    }
                    List<PatrolEquipmentParamsPo> equipmentParamsPos = t.getEquipmentParamsPos();
                    if (CollectionUtils.isNotEmpty(equipmentParamsPos)) {
                        equipmentParamsPos.forEach(paramsPo -> paramsPo.setId(null));
                    }
                });
            }
        });
    }

    /**
     * 生成巡视工单
     */
    @XxlJob(value = "generatePatrolOrder")
    public boolean generatePatrolOrder(PatrolPlanDetailVO detail) {
        // 默认创建失败
        boolean flag = false;
        // 任务类型，1->手动任务，2->定时任务运，不提前，3->定时任务，提前
        Integer taskType = null;
        CronTask cronTask = null;
        // 1、获取任务详情
        if (detail != null) {
            // 1、手动生成
            taskType = 1;
        } else {
            // 2、定时任务
            try (SessionFactory factory = new SessionFactory()) {
                cronTask = xxlJobService.getCronTaskByXxlJobId((int) XxlJobHelper.getJobId());
                // 如果是定时任务，则获取的默认数据源为system，需要进行切换
                EamUser user = ThreadLocalContext.get();
                user = user == null ? new EamUser() : user;
                user.setCurrDs(cronTask.getMark());
                ThreadLocalContext.set(user);
                Assert.notNull(cronTask, "定时任务为空");
                if (CommonUtils.parse(cronTask.getBusinessId()) > 0) {
                    // 2.1、定时任务-提前（临时任务，将临时任务id作为业务id查询出定时任务）
                    XxlJobHelper.log("定时任务-提前");
                    taskType = 3;
                    cronTask = xxlJobService.getCronTaskByXxlJobId(CommonUtils.parse(cronTask.getBusinessId()));
                } else {
                    // 2.2、定时任务
                    XxlJobHelper.log("定时任务");
                    taskType = 2;
                }
                factory.setDbEnv(cronTask.getMark());
                XxlJobHelper.log("mark = {}", cronTask.getMark());
                detail = queryById(factory, cronTask.getBusinessId());
                Assert.notNull(detail, "巡视计划详情不能为空");
            } catch (Exception e) {
                XxlJobHelper.log(e.getMessage(), e);
                log.error(e.getMessage(), e);
            }
        }
        log.info("巡视计划详情: {}", JSON.toJSONString(detail));
        XxlJobHelper.log("巡视计划定时任务类型： {}， 巡视计划详情: {}", taskType == 1 ? "手动任务" : (taskType == 2 && detail.getAdvanceHours() == 0 ? "定时任务[不提前]" : "定时任务[提前]" + detail.getAdvanceHours() + "时"), JSON.toJSONString(detail));

        XxlJobHelper.log("巡视计划数值：taskType=" + taskType + "提前：" + detail.getAdvanceHours());
        //如果是手动下发则直接生成
        if (taskType == 1) {
            // 2、调用工单方法生成工单
            flag = createPatrolOrder(detail);
            XxlJobHelper.log("手动下发工单生成结果 = {}", flag);
        }
        //不是手动下发,则采用延迟生成策略(先生成主表,后续生成子表内容)
        if ((taskType == 2 && !CommonUtils.isPositive(detail.getAdvanceHours())) || (taskType == 3 && CommonUtils.isPositive(detail.getAdvanceHours()))) {
            // 2、调用工单方法生成工单
            XxlJobHelper.log("开始执行主表延迟生成策略");
            flag = createPatrolOrderOnly(detail);
            XxlJobHelper.log("定时任务工单生成结果 = {}", flag);
        }
        // 3、如果是提前执行的任务，调用完工单后，删除当前任务，再生成下次执行的定点任务
        if (taskType == 3) {
            XxlJobHelper.log("更新下次执行的定点任务");
            // 3.1、更新下次执行的定点任务
            XxlJobInfo jobInfo = xxlJobService.getXxlJobInfoById(cronTask.getXxlJobId());
            Date firstTaskTime = getFirstExecuteTimes(detail.getCronTask().getCron(), detail.getAdvanceHours());
            String executeTCron = CommonUtils.getCron(firstTaskTime);
            XxlJobHelper.log("jobInfo = {}, cronTask = {},detail = {}", JSON.toJSONString(jobInfo), JSON.toJSONString(cronTask), JSON.toJSONString(detail));
            xxlJobService.update(String.valueOf(cronTask.getXxlJobId()), executeTCron, cronTask.getMark(), "jobId=" + cronTask.getXxlJobId() + "的临时一次性任务", detail.getUpdateBy(), jobInfo.getExecutorHandler());
            xxlJobService.start(String.valueOf(cronTask.getXxlJobId()));
            XxlJobHelper.log("下一次临时一次性任务更新完成, 时间 = {}", executeTCron);
        }
        if (taskType == 2 && CommonUtils.isPositive(detail.getAdvanceHours())) {
            XxlJobHelper.log("开始自救策略");
            // 临时任务cronTask
            CronTask scheduleTask = xxlJobService.getCronTaskById(String.valueOf(cronTask.getXxlJobId()));
            //临时任务 xxljob
            XxlJobInfo jobInfo = xxlJobService.getXxlJobInfoById(scheduleTask.getXxlJobId());
            if (jobInfo.getTriggerStatus() == 0) {
                Date firstTaskTime = getFirstExecuteTimes(detail.getCronTask().getCron(), detail.getAdvanceHours());
                String executeTCron = CommonUtils.getCron(firstTaskTime);
                XxlJobHelper.log("[自救]jobInfo = {}, cronTask = {},detail = {}", JSON.toJSONString(jobInfo), JSON.toJSONString(cronTask), JSON.toJSONString(detail));
                xxlJobService.update(String.valueOf(cronTask.getXxlJobId()), executeTCron, cronTask.getMark(), "jobId=" + cronTask.getXxlJobId() + "的临时一次性任务", detail.getUpdateBy(), jobInfo.getExecutorHandler());
                xxlJobService.start(String.valueOf(cronTask.getXxlJobId()));
                XxlJobHelper.log("[自救]下一次临时一次性任务更新完成, 时间 = {}", executeTCron);
            }
        }
        XxlJobHelper.log("巡视计划生成-执行结果 = {}", flag);
        //清除ThreadLocal信息,防止线程污染
        ThreadLocalContext.clear();
        return flag;
    }

    private Date getFirstExecuteTimes(String cron, Double hours) {
        int time = 2;
        // 下次执行的时间
        Date nextTime = null;
        while (nextTime == null || nextTime.compareTo(new Date()) <= hours * 3600 * 1000 || time > 1024) {
            List<String> executeTimes = xxlJobService.nextTriggerTime(cron, time);
            Assert.notEmpty(executeTimes, "不合法的cron表达式,请确认定时任务设定的时间,保存后重试");
            for (int i = 0, size = executeTimes.size(); i < size; i++) {
                try {
                    nextTime = DateUtils.parseDate(executeTimes.get(i), "yyyy-MM-dd HH:mm:ss");
                } catch (Exception e) {
                    log.error(e.getMessage(), e);
                }
                Assert.notNull(nextTime, "非法日期字符串， 要求时间格式: yyyy-MM-dd HH:mm:ss");
                if (nextTime.getTime() - hours * 3600 * 1000 > (System.currentTimeMillis() + 5 * 1000)) {
                    /** fixup: 其中5秒为预读时间，因为xxl-job会将当前到之后5秒内的任务全部取出，例如当前是13:59:55，取出了13:59:55~14:00:00的任务
                     * 但获取下一次执行时间时，发现14:00:00在当前时间13:59:55之后，误认为14:00:00是下次执行的时间，其实14:00:00是这次执行的时间，
                     * 只是xxl-job预读了后5秒的数据，导致任务提前几秒执行了。应用将14:00:00作为下次执行的时间，但xxl-job在13:59:55执行的其实是14:00:00要执行的任务，
                     * 故在14:00:00时，不会再将任务执行一次，导致下次的临时任务时间失效
                     */
                    return new Date(new Double(nextTime.getTime() - (hours * 3600 * 1000)).longValue());
                }
            }
            if (nextTime == null && executeTimes.size() == 1) {
                // 如果是定点任务， 且算上提前时间，任务执行的时间在当前时间之前
                throw new IllegalArgumentException("提前下达的时间过，该任务将无法执行");
            }
            time <<= 1;
        }
        return null;
    }


    @Override
    @TaskAnnotation("getPlanPatrolMsg")
    public PatrolDto getPlanPatrolMsg(SessionFactory factory, String planId, String orderCreateTime) {
        PatrolPlanDetailVO detail = queryById(factory, planId);
        long now = System.currentTimeMillis();
        try {
            PatrolDto patrolDto = convertor.planBasicToOrder(detail);
            List<PatrolPointVo> pointVos = new ArrayList<>();
            patrolDto.setPointVos(pointVos);
            // 默认待巡视状态
            patrolDto.setStatus(Constants.PATROL_ORDER_INIT_STATUS);
            // 1、计划主体
            // {巡视计划标题}_{YYYYMMDDHH}
            patrolDto.setTitle(detail.getName() + "_" + DateFormatUtils.format(new Date(), "yyyyMMddHH"));
            // 提前的小时数
            Double advanceHours = detail.getAdvanceHours();
            // 如果有提前时间，则往后推迟指定时间，计作计划开始时间
            Date startTime = new Date(now + (advanceHours == null ? 0 : new Double((advanceHours * 3600 * 1000)).longValue()));
            patrolDto.setPlanStartTime(startTime);
            patrolDto.setPlanEndTime(new Date(startTime.getTime() + new Double(detail.getPatrolDuration() * 3600 * 1000).longValue()));
            patrolDto.setCreateTime(new Date());
            patrolDto.setUpdateTime(new Date());
            // 2、巡视点
            List<PatrolPlanPointRelVO> rels = detail.getRels();
            // 所有巡视点id
            List<String> pointIds = rels.stream().map(PatrolPlanPointRelVO::getPointId).collect(Collectors.toList());
            // 批量查询所有的巡视点
            List<PatrolPlanPointDetailVO> pointDetailVo = pointService.queryBatchById(factory, pointIds);
            // 巡视点 id -> bean
            Map<String, PatrolPlanPointDetailVO> pointMap = pointDetailVo.stream().collect(Collectors.toMap(PatrolPlanPointDetailVO::getId, item -> item, (v1, v2) -> v2));
            // 所有巡视点的位置id
            List<String> locationIds = pointDetailVo.stream().map(PatrolPlanPointDetailVO::getLocationId).collect(Collectors.toList());
            // 批量查询所有巡视点的位置
            List<EquipmentLocation> locations = CommonUtils.ok(equipmentFeign.getByIdWithoutEquipment(locationIds));
            // 巡视点位置 id -> bean
            Map<String, EquipmentLocation> locationMap = locations.stream().collect(Collectors.toMap(EquipmentLocation::getId, item -> item, (v1, v2) -> v2));
            for (PatrolPlanPointRelVO rel : rels) {
                PatrolPlanPointDetailVO patrolPlanPointDetailVO = pointMap.get(rel.getPointId());
                PatrolPointVo patrolPointVo = convertor.pointDetailToOrderPoint(patrolPlanPointDetailVO);
                // 设置顺序
                patrolPointVo.setSort(rel.getSort());
                EquipmentLocation location = locationMap.get(patrolPlanPointDetailVO.getLocationId());
                XxlJobHelper.log("巡视计划-巡视设备位置: {}", JSON.toJSONString(location));
                if (!Objects.isNull(location) && StringUtils.isNotEmpty(location.getLatlon()) && StringUtils.split(location.getLatlon(), ",").length == 2) {
                    // 校验经纬度存在且可正确拿到，latlon格式：经度,纬度
                    patrolPointVo.setLongitude(StringUtils.split(location.getLatlon(), ",")[0]);
                    patrolPointVo.setLatitude(StringUtils.split(location.getLatlon(), ",")[1]);
                    patrolPointVo.setRange(location.getRange());
                }
                pointVos.add(patrolPointVo);
            }
            // 3、巡视-值班人员
       /*     if(Constants.StaffingWay.map.containsKey(detail.getStaffingWay())) {
                // 没有指定人员
                List<String> staffTypes = new ArrayList<>();
                staffTypes.add(Constants.StaffingWay.map.get(detail.getStaffingWay()));
                ShiftRecordQuery shiftQuery = new ShiftRecordQuery();
                // 条件1：部门
                shiftQuery.setDeptId(detail.getPatrolDeptId());
                // 条件2：位置
                shiftQuery.setLocationId(detail.getLocationId());
                // 条件3：排班方式
                shiftQuery.setStaffTypes(staffTypes);
                ChangeShiftDTO shiftDTO = CommonUtils.ok(shiftFeign.info(shiftQuery));
                if(shiftDTO != null && shiftDTO.getThisClassesShift() != null) {
                    ClassesShiftDTO thisShift = shiftDTO.getThisClassesShift();
                    List<ClassesStaff> staffs = thisShift.getStaffs();
                    // 如果巡视工单的预计开始时间在本值结束时间之前，则将本值人员设置为巡视人员
                    // if(thisShift.getEndTime().compareTo(patrolDto.getPlanStartTime()) > 0 && !CollectionUtils.isEmpty(staffs)) {
                    if(!CollectionUtils.isEmpty(staffs)) {
                        List<PatrolUserPo> userPos = new ArrayList<>();
                        Stream.iterate(0, i -> i + 1).limit(staffs.size()).forEach(i -> {
                            ClassesStaff staff = staffs.get(i);
                            userPos.add(new PatrolUserPo(staff.getStaffBy(), staff.getStaffName(), i + 1));
                        });
                        patrolDto.setUserPos(userPos);
                    }
                }
            }
            if(!Constants.StaffingWay.map.containsKey(detail.getStaffingWay())) {
                // 指定巡视人员
                List<PatrolUserPo> userPos = new ArrayList<>();
                List<PatrolPlanWorkerBean> workers = detail.getWorkers();
                Stream.iterate(0, i -> i + 1).limit(workers.size()).forEach(i -> {
                    PatrolPlanWorkerBean worker = workers.get(i);
                    userPos.add(new PatrolUserPo(worker.getWorkerId(), worker.getWorkerName(), i + 1));
                });
                patrolDto.setUserPos(userPos);
            }*/
            //处理生成的工单数据
            dealPatrolDto(patrolDto);
            return patrolDto;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return null;
        }
    }
}
